#include "pfm.h"
#include "utils.h"

#include <string>
#include <fstream>

void byteSwapBytes(unsigned char * b, int n)
{
    register int i = 0;
    register int j = n-1;
    while (i<j)
    {
        std::swap(b[i], b[j]);
        i++, j--;
    }
}

template <typename T>
void byteSwap(T *toswap)
{
    byteSwapBytes(reinterpret_cast<unsigned char *>(toswap),sizeof(T));
}

std::string readUntil(std::istream & s, char c)
{
    std::string result;
    std::getline(s, result, c);
    return result;
}

bool IsBigEndian()
{
    short word = 0x4321;
    if((*(char *)& word) != 0x21 )
        return true;
    else
        return false;
}

#define LOG(stuff) if (output) (*output) << stuff

const vector2d <HDRPIXEL> loadPfm(const char * file, std::ostream * output)
{
    vector2d <HDRPIXEL> result;

    std::ifstream f(file, std::ifstream::in | std::ifstream::binary);

    if (!f)
    {
        LOG("Unable to open file: " << file);
        return result;
    }

    const int newline = 10;

    if (f.get() != 'P' || f.get() != 'F' || f.get() != newline)
    {
        LOG("Expected PF identifier");
        return result;
    }

    // read width, height, and aspect (negative aspect means little endian)
    // these can be newline or space delimited, so make up to 3 passes
    std::vector <std::string> params;
    params.reserve(3);
    for (int loops = 0; loops < 3 && params.size() < 3; loops++)
    {
        std::string in = readUntil(f, 10);
        std::vector <std::string> tokenized = explode(in, " ");
        params.insert(params.end(), tokenized.begin(), tokenized.end());
    }

    if (params.size() != 3)
    {
        LOG("Expected width, height, and aspect (either newline or space delimited) but got " << params.size() << " parameters instead");
        return result;
    }

    // decode parameter strings
    int width = fromstr<int>(params[0]);
    int height = fromstr<int>(params[1]);
    float aspect = fromstr<float>(params[2]);
    bool fileIsLittleEndian = (aspect < 0);
    bool machineIsLittleEndian = !IsBigEndian();

    bool swap = false;
    if (machineIsLittleEndian != fileIsLittleEndian)
    {
        swap = true;
    }

    result.columns = width;
    result.data.resize(width*height);

    for (int i = 0; i < width * height && f; i++)
    {
        HDRPIXEL &p = result.data[i];
        f.read(reinterpret_cast<char*>(&p), sizeof(HDRPIXEL));
        if (swap)
        {
            byteSwap(&p.r);
            byteSwap(&p.g);
            byteSwap(&p.b);
        }

        if (!f)
        {
            LOG("End of file encountered earlier than expected");
        }
    }

    return result;
}

